#region Copyright & License
/* Tyng.Data
 * Copyright (c) 2007, Paul Tyng, All rights reserved.
 * Code licensed under the BSD License:
 * http://www.opensource.org/licenses/bsd-license.php
 * Downloaded from: http://code.google.com/p/tyngdata/
*/
#endregion

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.IO;

namespace Tyng.Data
{
	/// <summary>
	/// A data reader read fixed length records 
	/// </summary>
	public class FixedLengthDataReader : TextReaderDataReader
	{
        FixedLengthField[] _fields;
        int _recordLength;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="recordLength"></param>
        /// <param name="fields"></param>
        public FixedLengthDataReader(TextReader reader, int recordLength, params FixedLengthField[] fields)
            : base(reader)
        {
            _recordLength = recordLength;
            SetFields(fields, false, false);

            //validate record length with last field
            FixedLengthField last = _fields[_fields.Length - 1];

            if ((last.Start + last.Length) > recordLength)
                throw new ArgumentOutOfRangeException("recordLength", "The record length is less than the last field's start + length");
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="fields"></param>
        public FixedLengthDataReader(TextReader reader, params FixedLengthField[] fields)
            : base(reader)
        {
            SetFields(fields, true, false);
        }

        /// <summary>
        /// Set the reader's field collection.
        /// </summary>
        /// <remarks>
        /// If necessary this will validate the passed fields, check for overlaps and 
        /// calculate the record length (assuming no unused space).
        /// </remarks>
        /// <param name="fields">Passed field array</param>
        /// <param name="setRecordLength">Flag indicating to calculate the record length</param>
        /// <param name="allowOverlap">Flag indicating to ignore overlapping fields</param>
        protected void SetFields(FixedLengthField[] fields, bool setRecordLength, bool allowOverlap)
        {
            if (fields == null || fields.Length == 0) throw new ArgumentNullException("fields");

            //sort, but don't save sorted, otherwsie index access is off...
            List<FixedLengthField> sorted = new List<FixedLengthField>(fields);
            sorted.Sort(
                delegate(FixedLengthField x, FixedLengthField y) 
                {
                    if (x.Start < y.Start) return -1;
                    if (x.Start == y.Start) return 0;
                    return 1;
                });

            //check for overlaps
            if (! allowOverlap && sorted.Count > 1)
            {
                for (int i = 0; i < sorted.Count- 1; i++)
                {
                    FixedLengthField current = sorted[i];
                    if (sorted[i + 1].Start < (current.Start + current.Length)) throw new FixedLengthFieldOverlapException();
                }
            }

            _fields = fields;

            if (setRecordLength)
            {
                FixedLengthField last = sorted[sorted.Count - 1];

                _recordLength = last.Start + last.Length;
            }
        }

		/// <summary>
		/// Reads the stream and creates a FixedLengthDataRecord instance
		/// </summary>
		/// <param name="detectionCharsAlreadyRead">Any peek ahead chars used to prepend to the read data</param>
		/// <param name="fullRecordLength">Total length of a record</param>
		/// <param name="fields">Field definitions</param>
		/// <returns>A fixed length data record instance for the current record, or null if no records</returns>
		protected FixedLengthDataRecord ReadFixedLengthRecord(char[] detectionCharsAlreadyRead, int fullRecordLength, FixedLengthField[] fields)
		{
			char[] record = new char[fullRecordLength];
			//insert at the beginning

            int detectionChars = 0;

            if (detectionCharsAlreadyRead != null)
            {
                detectionChars = detectionCharsAlreadyRead.Length;
                detectionCharsAlreadyRead.CopyTo(record, 0);
            }
			
			//read the rest of the record.
            int stillToRead = fullRecordLength - detectionChars;
			if (stillToRead > 0)
			{
				int charsRead = TextReader.Read(record, detectionChars, stillToRead);
				if (stillToRead > charsRead)
					if (detectionChars > 0)
						throw new EndOfStreamException("Unable to read the entire record");
					else
						return null;
			}
			return new FixedLengthDataRecord(record, fields);
        }

        /// <summary>
        /// Get the next data record from the reader and advance the position
        /// </summary>
        /// <returns>A new data record</returns>
        protected override IDataRecord GetNextDataRecord()
        {
            char[] peek = new char[1];

            int peekCount = TextReader.Read(peek, 0, 1);

            if (peekCount == 0)
                return null;

            return ReadFixedLengthRecord(peek, _recordLength, _fields);
        }

        /// <remarks />
        protected override Field[] DataFields
        {
            get { return _fields; }
        }
    }

    /// <summary>
    /// Exception thrown when any fixed length fields overlap.
    /// </summary>
    public class FixedLengthFieldOverlapException : Exception
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public FixedLengthFieldOverlapException() { }
    }
}
